package com.heima.wemedia.service.impl;


import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.common.constants.WemediaConstants;
import com.heima.common.constants.WmNewsMessageConstants;
import com.heima.common.exception.CustomException;
import com.heima.model.common.dtos.PageResponseResult;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.model.common.enums.AppHttpCodeEnum;
import com.heima.model.wemedia.dtos.NewsAuthDto;
import com.heima.model.wemedia.dtos.WmNewsDto;
import com.heima.model.wemedia.dtos.WmNewsPageReqDto;
import com.heima.model.wemedia.pojos.WmMaterial;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.model.wemedia.pojos.WmNewsMaterial;
import com.heima.model.wemedia.pojos.WmUser;
import com.heima.model.wemedia.vo.WmNewsVo;
import com.heima.utils.thread.WmThreadLocalUtil;
import com.heima.wemedia.mapper.WmMaterialMapper;
import com.heima.wemedia.mapper.WmNewsMapper;
import com.heima.wemedia.mapper.WmNewsMaterialMapper;
import com.heima.wemedia.mapper.WmUserMapper;
import com.heima.wemedia.service.WmNewsAutoScanService;
import com.heima.wemedia.service.WmNewsService;
import com.heima.wemedia.service.WmNewsTaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@Transactional
public class WmNewsServiceImpl extends ServiceImpl<WmNewsMapper, WmNews> implements WmNewsService {


    /**
     * 条件查询文章列表
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findList(WmNewsPageReqDto dto) {
        //1.校验参数
        //分页检查
        dto.checkParam();//来自WmNewsPageReqDto所继承的PageRequestDto
        //2.分页查询
        IPage page = new Page(dto.getPage(),dto.getSize());
        LambdaQueryWrapper<WmNews> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        page(page,lambdaQueryWrapper);
        //状态精确查询
        if(dto.getStatus()!=null){
            lambdaQueryWrapper.eq(WmNews::getStatus,dto.getStatus());
        }
        //频道精确查询
        if(dto.getChannelId()!=null){
            lambdaQueryWrapper.eq(WmNews::getChannelId,dto.getChannelId());
        }
        //时间范围查询
        if(dto.getBeginPubDate()!=null&&dto.getEndPubDate()!=null){
            lambdaQueryWrapper.between(WmNews::getPublishTime,dto.getBeginPubDate(),dto.getEndPubDate());
        }
        //关键字的模糊查询(因为当前关键字是字符串，需要判空)
//        if(dto.getKeyword()!=null&&dto.getKeyword().length()!=0){
        if(StringUtils.isNoneBlank(dto.getKeyword())){
            lambdaQueryWrapper.like(WmNews::getTitle,dto.getKeyword());
        }
        //查询当前登陆人的文章(需要从线程中获取用户)
        lambdaQueryWrapper.eq(WmNews::getUserId, WmThreadLocalUtil.getUser().getId());

        //按照发布时间倒叙查询
        lambdaQueryWrapper.orderByDesc(WmNews::getCreatedTime);

        page = page(page,lambdaQueryWrapper);

        //3.结果返回
        ResponseResult pageResponseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)page.getTotal());

        pageResponseResult.setData(page.getRecords());
        return pageResponseResult;
    }

    @Autowired
    private WmNewsAutoScanService wmNewsAutoScanService;

    @Autowired
    private WmNewsTaskService wmNewsTaskService;
    /**
     * 发布保存/修改 文章 或 保存为草稿
     * @param dto
     * @return
     */
    @Override
    public ResponseResult submitNews(WmNewsDto dto) {
        //0.校验参数
        if(dto == null || dto.getContent() == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"文章内容不能为空");
        }

        //1.保存或修改文章
        WmNews wmNews = new WmNews();
        //拷贝公共属性  属性名称和内容相同时才能拷贝
        BeanUtils.copyProperties(dto,wmNews);
        //封面图片， (dto)list->(wmNews)string
        if(dto.getImages()!=null && dto.getImages().size()>0){
            //[1.jpg,2.jpg,3.jpg] --> "1.jpg,2.jpg,3.jpg"
            wmNews.setImages(StringUtils.join(dto.getImages(), ","));
        }
        //如果当前封面类型为自动 -1 , 则以文章内容的图片作为封面图片,
        //规则是：内容中无图：封面无图，
        //      内容中图片1-3：封面单图
        //      内容中图片大于3张：封面多图(三张)
        // 后面第四步再处理 ,0是无图，1是单图，3是多图，-1是自动
        if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
            wmNews.setType(null);
            //封面为自动，就清空，等在到下面的saveRelationsInfoForCover里再根据规则填充
            //规则：* 1，内容图片数量 1<= <3 单图 type=1
            //     * 2，内容图片数量 >=3 多图 type=3
            //     * 3，无图，无封面 type=0
        }
        //不管是不是草稿，都要先保存（草稿也是文章的一种）
        // 保存(不存在id)或(已存在id,说明之前已经提交/保存成草稿了)修改文章(修改文章需要删除已关联素材关系，
        // 之后需要判断他是否是草稿，如果不是草稿，需要重新保存文章内容WmNews图片与素材WmMaterial的关系
        // (前端有两个按钮，一个是“存入草稿”，一个是“提交审核”，用的是同一个api路径？？？))
        saveOrUpdateWmNews(wmNews);

        //2.判断是否为草稿，如果是(草稿不用保存关联关系？？草稿里引用的素材可以任意删除？？？
        // 草稿可能没有引用任何素材？？万一引用了呢)，
        // 则直接结束当前方法？？？？？？保存草稿到前端？？？
        //WmNews的状态枚举类//状态用数字表示，阅读不友好，直接调用Status.NORMAL.....来替代数字比较好
        if(dto.getStatus().equals(WmNews.Status.NORMAL.getCode())){
            //WmNews.Status 0 草稿 1 提交（待审核） 2 审核失败 3 人工审核 4 人工审核通过 8 审核通过（待发布） 9 已发布
            return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);//保存成一条文章的数据????
        }

        //3.1不是草稿, 保存文章内容WmNews图片与素材WmMaterial的关系(在saveOrUpdateWmNews里面的修改文章时删除了原有的关系)
        //获取到前端传来的文章内容中的图片信息(图片路径)
        List<String> materialsUrl = extraUrlInfo(dto.getContent());
        //用内容中的图片url去获取materialId，文章的id
        saveRelationsInfoForContent(materialsUrl,wmNews.getId());

        //3.2如果当前封面布局是自动，需要匹配封面图片(到当前内容中的图片进行匹配)，并保存文章与素材的关系
        saveRelationsInfoForCover(dto,wmNews,materialsUrl);

        //返回之前进行审核
        // 1.在启动类WemediaApplication里开启异步调用@EnableAsync
        // 2.在autoScanWmNews的实现类里加上@Async//标明当前方法是异步的，异步执行，不会阻塞主线程
        System.out.println("异步调用可能会导致：上一步saveRelativeInfoForCover()方法" +
                " 还没成功保存wmNews，这里通过id查不到wmNews而为null ? "+wmNews.getId());
        //可以设置一个线程睡眠1秒。
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
//        System.out.println("wmNews.getId()拿到了吗："+wmNews.getId());

        //4 发布文章后异步调用文章审核服务(里面包含多种审核技术)，因为
        // 1.审核文章不需要返回值
        // 2.通常审核的时间都比较长
//        wmNewsAutoScanService.autoScanWmNews(wmNews.getId()); //不去直接调用，把它交给当前的任务，然后再去审核文章
        wmNewsTaskService.addNewsToTask(wmNews.getId(), wmNews.getPublishTime());

        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }



    /**
     * 功能一: 如果当前封面类型为自动，则设置封面类型的数据
     * 匹配规则：
     * 1，内容图片 1<= <3 单图 type=1
     * 2，内容图片 >=3 多图 type=3
     * 3，无图，无封面 type=0
     *
     * 功能二：保存封面图片与素材WmMaterial的关系
     * @param dto
     * @param wmNews
     * @param materialsUrl
     */
    private void saveRelationsInfoForCover(WmNewsDto dto, WmNews wmNews, List<String> materialsUrl) {

        //需要截取当前内容中的图片，到文章作为封面
        //如果有封面图片，直接从这里获取
        List<String> images = dto.getImages();

        //如果(没有封面)当前封面类型为自动，意味着原文章封面属性是空的，则需要截取素材图片赋值给封面属性
        if(dto.getType().equals(WemediaConstants.WM_NEWS_TYPE_AUTO)){
//            if(materialsUrl.size()>0){
                //1，内容图片 1<= <3 单图 type=1
                if(materialsUrl.size() >= 1 && materialsUrl.size() < 3){
                    wmNews.setType(WemediaConstants.WM_NEWS_SINGLE_IMAGE);
                    images = materialsUrl.stream().limit(1).collect(Collectors.toList());
                }else if(materialsUrl.size() >= 3){
                    //2，内容图片 >=3 多图 type=3
                    wmNews.setType(WemediaConstants.WM_NEWS_MANY_IMAGE);
                    images = materialsUrl.stream().limit(3).collect(Collectors.toList());
                }else{
                    //3，无图，无封面 type=0
                    wmNews.setType(WemediaConstants.WM_NEWS_NONE_IMAGE);
                }

                //因为对type属性重新赋值了，所以需要重新保存文章数据
                if(images != null && images.size() > 0){//设置了文章的封面属性，都要保存到数据库
                    wmNews.setImages(StringUtils.join(images,","));
                }
                updateById(wmNews);
//            }
        }

        //不管是哪一种情况(有没有封面)，都要保存文章与素材的关系
        if(images != null && images.size() > 0){
            //保存文章与素材的关系
            saveRelativeInfo(images, wmNews.getId(), WemediaConstants.WM_COVER_REFERENCE);
            //WM_COVER_REFERENCE 1 当前封面类型的引用
        }

    }

    /**
     * 处理文章内容WmNews图片与素材WmMaterial的关系
     * (记录该文章 内容/封面 用到了什么素材的图片，到素材文章关系表中)
     * @param materialsUrl
     * @param newsId
     */
    private void saveRelationsInfoForContent(List<String> materialsUrl, Integer newsId){
        //保存封面图片与素材WmMaterial的关系//0是内容引用，1是主图(封面素材)引用
        saveRelativeInfo(materialsUrl, newsId, WemediaConstants.WM_CONTENT_REFERENCE);
    }

    @Autowired
    private WmMaterialMapper wmMaterialMapper;

    /**
     * 保存文章WmNews(内容的/封面的)图片与素材WmMaterial到数据库中
     * @param materialsUrl
     * @param newsId
     * @param type
     */
    private void saveRelativeInfo(List<String> materialsUrl, Integer newsId, Short type) {
        //参数判断，如果materialsUrl是空(封面图片和内容图片都没有(没有引用任何素材？？？))，则不需要保存关联关系
        if(materialsUrl != null || !materialsUrl.isEmpty()){//materials.size() > 0
            //通过图片的url查询素材的id
            List<WmMaterial> wmMaterials = wmMaterialMapper
                    .selectList(Wrappers.<WmMaterial>lambdaQuery().in(WmMaterial::getUrl, materialsUrl));

            //判断素材是否有效（写草稿期间，引入了素材，还没保存提交，素材就被删了，的情况）
            if(wmMaterials == null || wmMaterials.size() == 0){
                //手动抛出异常，1.提示调用者的素材失效了，2.进行数据回滚（回滚saveOrUpdateWmNews的保存操作）
                throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);//,"素材失效，请重新选择"
            }

            //判断如果materialsUrl的数量和wmMaterials的数量不一致，则抛出异常，提示调用者，内容中的图片数量和素材数量不一致
            if(materialsUrl.size() != wmMaterials.size()){
                throw new CustomException(AppHttpCodeEnum.MATERIASL_REFERENCE_FAIL);//,"内容中的图片数量和素材数量不一致，请重新选择"
            }

            List<Integer> materialIds = //整理出一堆Id
                    wmMaterials.stream().map(WmMaterial::getId).collect(Collectors.toList());

            //批量保存
            System.out.println("sql在哪错了，发布文章时，别选择上面的'选择时间'和'选择日期");
            wmNewsMaterialMapper.saveRelations(materialIds, newsId, type);
        }


    }

    /**
     * 获取文章内容中的图片信息
     * @param content
     * @return
     */
    private List<String> extraUrlInfo(String content) {
        List<String> materialsUrl = new ArrayList<>();
        //content是一个json,把它转成集合 //转成math.class
        List<Map> maps = JSON.parseArray(content,Map.class);
        for (Map map : maps) {
            //接口的返回值是json，需要转成集合
            //"content":"[
            // {
            //  "type":"type",
            //  "value":"文章内容"
            // },
            // {
            //  "type":"image",
            //  "value":"图片路径"
            // }
            // ]"
            if(map.get("type").equals("image")){//image要和前端的WmNewsDto里的content里的"type":"image"对应
                String imgUrl = (String) map.get("value");
                materialsUrl.add(imgUrl);
            }
        }
        return materialsUrl;
    }

    @Autowired
    private WmNewsMaterialMapper wmNewsMaterialMapper;

    /**
     * 保存或修改文章
     * @param wmNews
     */
    private void saveOrUpdateWmNews(WmNews wmNews) {
        //补全属性
        wmNews.setUserId(WmThreadLocalUtil.getUser().getId());
//        wmNews.setCreatedTime(new Date());
        wmNews.setSubmitedTime(new Date());
        wmNews.setEnable((short)1);//默认上架
        if(wmNews.getId()==null){//前端传来的参数不存在文章id,说明是刚写好的文章，需要保存
            //保存文章
            wmNews.setCreatedTime(new Date());
            save(wmNews);
        }else{//前端传来的参数存在文章id，说明是修改文章(或草稿？)，是修改操作

            // 该文章用到了哪些素材，文章和文章素材是多对多的关系，一篇文章可以引用多个素材，一个素材也可以被多个文章使用
            //wmNewsMaterial是中间表，素材如果被文章引用了，就不能删除(套餐里的被引用的菜品不能删除)
            //删除文章与素材的关系，先删除素材与文章的关系(文章素材关系表里记录了关系)，再重新新增关系
            // (因为修改时可能修改了引用的素材图片，无脑全部删除关系，后面再新增即可)
            wmNewsMaterialMapper.delete//删除该文章引用的所有素材的关系，所以要以文章id为条件，去删除文章素材表的记录
                    (Wrappers.<WmNewsMaterial>lambdaQuery().eq(WmNewsMaterial::getNewsId,wmNews.getId()));
            //重新更新文章的创建时间和提交时间等信息
            updateById(wmNews);
        }

    }

    /**
     * 查询文章详情
     * @param id
     * @return
     */
    @Override
    public ResponseResult findOne(Integer id) {
        //1.检查参数
        if(id == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.查询文章
        WmNews wmNews = getById(id);
        if(wmNews == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }
        //3.结果返回
        return ResponseResult.okResult(wmNews);
    }

    @Autowired
    private KafkaTemplate<String,String> kafkaTemplate;

    /**
     * 文章上下架
     * @param dto
     * @return
     */
    @Override
    public ResponseResult downOrUp(WmNewsDto dto) {
        //1.检查参数
        if(dto.getId() == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"文章id不能为空");
        }
        //2.查询文章，判断文章是否存在，存在才能下架
        WmNews wmNews = getById(dto.getId());
        if(wmNews == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"文章不存在");
        }
        //3.判断文章是否已发布，已发布才能下架
        if(!wmNews.getStatus().equals(WmNews.Status.PUBLISHED.getCode())){ //wmNews.getStatus() != null &&
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"文章未发布，无法下架");
        }

        //4.修改文章enable(short整数？？)字段，实现上下架(kafu..中间件通知文章下架)
        if(wmNews.getEnable() != null && dto.getEnable() > -1 && dto.getEnable() < 2) { //dto.getEnable()是0下架状态或者1上架状态
            //根据文章id修改文章的enable字段
            //方法一
//            wmNews.setEnable(dto.getEnable());
//            wmNewsMapper.updateById(wmNews);
            //方法二
            update(Wrappers.<WmNews>lambdaUpdate().set(WmNews::getEnable,dto.getEnable()).eq(WmNews::getId,dto.getId()));
            //方法三？？？？
//            LambdaUpdateWrapper<WmNews> updateWrapper =
//                    Wrappers.<WmNews>lambdaUpdate().set(WmNews::getEnable,dto.getEnable()).eq(WmNews::getId,dto.getId());

            //如果文章id都没有，说明文章id是null，此时文章不存在，不需要下架
            if(wmNews.getArticleId() != null){
                //发送消息，通知article端修改文章的配置         消息主题：wm.news.up.or.down.topic(谁订阅了，谁就能接收到)
                Map<String,Object> map = new HashMap<>();
                map.put("articleId",wmNews.getArticleId());
                map.put("enable",dto.getEnable());
                //map转成json，article端接收到之后再转回map
                kafkaTemplate.send(WmNewsMessageConstants.WM_NEWS_UP_OR_DOWN_TOPIC,JSON.toJSONString(map));
                log.info("WmNewsServiceimpl-文章上下架消息发送成功！map:{}",map);
                //然后在文章那端接收消息，修改文章的配置
            }

        }
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

    @Autowired
    private WmNewsMapper wmNewsMapper;

    /**
     * 查询文章列表
     * @param dto
     * @return
     */
    @Override
    public ResponseResult findList(NewsAuthDto dto) {
        //1.参数检查
        dto.checkParam();

        //记录当前页
        int currentPage = dto.getPage();

        //2.分页查询+count查询
        dto.setPage((dto.getPage()-1)*dto.getSize());
        List<WmNewsVo> wmNewsVoList = wmNewsMapper.findListAndPage(dto);
        int count = wmNewsMapper.findListCount(dto);

        //3.结果返回
        ResponseResult responseResult = new PageResponseResult(currentPage,dto.getSize(),count);
        responseResult.setData(wmNewsVoList);
        return responseResult;
    }

    @Autowired
    private WmUserMapper wmUserMapper;

    /**
     * 查询文章详情
     * @param id
     * @return
     */
    @Override
    public ResponseResult findWmNewsVo(Integer id) {
        //1.检查参数
        if(id == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.查询文章信息
        WmNews wmNews = getById(id);
        if(wmNews == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        //3.查询用户信息
        WmUser wmUser = wmUserMapper.selectById(wmNews.getUserId());

        //4.封装vo返回
        WmNewsVo vo = new WmNewsVo();
        //属性拷贝
        BeanUtils.copyProperties(wmNews,vo);
        if(wmUser != null){
            vo.setAuthorName(wmUser.getName());
        }

        ResponseResult responseResult = new ResponseResult().ok(vo);

        return responseResult;
    }

    /**
     * 文章审核，修改状态
     * @param status 2  审核失败  4 审核成功
     * @param dto
     * @return
     */
    @Override
    public ResponseResult updateStatus(Short status, NewsAuthDto dto) {
        //1.检查参数
        if(dto == null || dto.getId() == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
        }
        //2.查询文章信息
        WmNews wmNews = getById(dto.getId());
        if(wmNews == null){
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
        }

        //3.修改文章的状态
        wmNews.setStatus(status);
        if(StringUtils.isNotBlank(dto.getMsg())){
            wmNews.setReason(dto.getMsg());
        }
        updateById(wmNews);

        //审核成功，则需要创建app端文章数据，并修改自媒体文章
        if(status.equals(WemediaConstants.WM_NEWS_AUTH_PASS)){
            //创建app端文章数据
            ResponseResult responseResult = wmNewsAutoScanService.saveAppArticle(wmNews);
            if(responseResult.getCode().equals(200)){
                wmNews.setArticleId((Long) responseResult.getData());
                wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
                updateById(wmNews);
            }
        }

        //4.返回
        return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
    }

}
